/* 
 * Copyright 2009 Shared Object (www.sharedobject.ch)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package edos.prm.role;

import java.util.HashSet;
import java.util.Set;

import edos.prm.activity.Operation;
import edos.prm.actor.Actor;
import edos.prm.actor.Actor.PrmActor;
import edos.prm.artifact.Artifact;
import edos.prm.artifact.Attribute;
import edos.prm.engine.PrmSecurityManager;
import edos.prm.project.Project;

/**
 * A {@code Role} is any responsibility that {@code Actor}s may undertake
 * within a {@code Project}.
 * <p>
 * A role is associated with a set of {@code Right}s. Each {@code Right}
 * permits the execution of a number of activity {@code Operation}s. A task
 * (executing process) must possess a role to execute operations.
 * <p>
 * {@code Role}s are organized hierarchically in order to simplify role
 * management. A task that possesses a role, by transitivity possesses all of
 * its sub-roles.
 * 
 * @version 1.0
 */
public interface Role extends Artifact {

	/**
	 * The {@code ROOT_ROLE} is the basic role assigned to the root actor
	 * on PRM start up. {@code ROOT_ROLE} is also the root of the role
	 * hierarchy.
	 */
	public static final Role ROOT_ROLE = new PrmRole((PrmActor) Actor.ROOT_ACTOR,
			"RootRole");
	
	/**
	 * Returns the name of the Role
	 * <p>
	 * The name of a role should be unique. Each implementation of the
	 * {@code Role} concept should guarantee name unicity.
	 * 
	 * @return the String name of the Role
	 */
	@Attribute
	public String getName();

	/**
	 * Returns a Description of the Role
	 * 
	 * @return a Description of the Role in Text form
	 */
	public String getDescription();

	/**
	 * Returns the rights that this role provides
	 * 
	 * @return an array of Rights that the Role provides
	 */
	public Right[] getRights();

	/**
	 * Return all rights associated with this role: the rights of this role
	 * and of all of its declared sub-roles.
	 * 
	 * @return an array of rights associated with this and descendant roles
	 */
	public Set<Right> getAllRights();
	
	/**
	 * Returns the parent Role.
	 * <p>
	 * If no parent role was explicitly declared for the role, then ROOT_ROLE
	 * is returned. If this is the ROOT_ROLE, then null is returned.
	 * 
	 * @return the parent Role
	 */
	public Role getParent();

	/**
	 * Returns all sub-Roles of this Role.
	 * 
	 * @return all sub-Roles as an array
	 */
	public Set<Role> getChildren();

	/**
	 * Gives the name of the actor who created this role
	 * 
	 * @return the actor who created this role.
	 */
	public Actor definedBy();

	/**
	 * Gives the project foe which this role is defined
	 * 
	 * @return the project behind this role
	 */
	@Attribute
	public Project getProject();

	/**
	 * Default Java implementation of the Role concept
	 */
	final static class PrmRole implements Role {

		private Actor definer;
		private HashSet<Role> children;
		private String name;
		private Project project;
		private String description;
		private HashSet<Right> rights;
		private Role parent;
		private static HashSet<String> roleNames;

		static {
			roleNames = new HashSet<String>();
		}
		
		public PrmRole(Actor.PrmActor definer, String name, String description,
				Role.PrmRole parent, Set<Operation> ops) {
			this.definer = definer;
			this.name = name;
			if ( roleNames.contains(name) )
				throw new IllegalArgumentException("Role name already in use"
						+ name);
			else
				roleNames.add(name);
			this.description = description;
			this.rights = new HashSet<Right>();
			for ( Operation op : ops )
				this.rights.add(new Right(op));
			this.parent = parent;
		}

		PrmRole(Actor.PrmActor definer, String name) {
			this.definer = definer;
			this.name = name;
			roleNames.add(name);
			this.description = "Default Role";
			this.rights = new HashSet<Right>();
			this.rights.add(Right.AllRights);
			this.parent = null;
		}

		public Actor definedBy() {
			PrmSecurityManager psm =
				(PrmSecurityManager)System.getSecurityManager();
			if ( psm != null )
				psm.verifyArtifactContainment(RoleManager.class, this.getClass());
			return definer;
		}

		public Set<Role> getChildren() {
			PrmSecurityManager psm =
				(PrmSecurityManager)System.getSecurityManager();
			if ( psm != null )
				psm.verifyArtifactContainment(RoleManager.class, this.getClass());
			HashSet<Role> result = new HashSet<Role>();
			result.addAll(children);
			return result;
		}

		public void addChildRole(Role role) {
			PrmSecurityManager psm =
				(PrmSecurityManager)System.getSecurityManager();
			if ( psm != null )
				psm.verifyArtifactContainment(RoleManager.class, this.getClass());
			children.add(role);
		}

		public void removeChildRole(Role role) {
			PrmSecurityManager psm =
				(PrmSecurityManager)System.getSecurityManager();
			if ( psm != null )
				psm.verifyArtifactContainment(RoleManager.class, this.getClass());
			children.remove(role);
		}

		public String getDescription() {
			PrmSecurityManager psm =
				(PrmSecurityManager)System.getSecurityManager();
			if ( psm != null )
				psm.verifyArtifactContainment(RoleManager.class, this.getClass());
			return this.description;
		}

		public String getName() {
			PrmSecurityManager psm =
				(PrmSecurityManager)System.getSecurityManager();
			if ( psm != null )
				psm.verifyArtifactContainment(RoleManager.class, this.getClass());
			return this.name;
		}

		public Role getParent() {
			PrmSecurityManager psm =
				(PrmSecurityManager)System.getSecurityManager();
			if ( psm != null )
				psm.verifyArtifactContainment(RoleManager.class, this.getClass());
			if ( ( parent == null ) && !( this == ROOT_ROLE ) )
				return ROOT_ROLE;
			else
				return this.parent;
		}

		public Project getProject() {
			PrmSecurityManager psm =
				(PrmSecurityManager)System.getSecurityManager();
			if ( psm != null )
				psm.verifyArtifactContainment(RoleManager.class, this.getClass());
			return project;
		}

		public Right[] getRights() {
			PrmSecurityManager psm =
				(PrmSecurityManager)System.getSecurityManager();
			if ( psm != null )
				psm.verifyArtifactContainment(RoleManager.class, this.getClass());
			Right[] result = new Right[rights.size()];
			rights.toArray(result);
			return result;
		}

		public void addRight(Right right) {
			PrmSecurityManager psm =
				(PrmSecurityManager)System.getSecurityManager();
			if ( psm != null )
				psm.verifyArtifactContainment(RoleManager.class, this.getClass());
			rights.add(right);
		}

		public void removeRight(Right right) {
			rights.remove(right);
		}

		public Set<Right> getAllRights() {
			PrmSecurityManager psm =
				(PrmSecurityManager)System.getSecurityManager();
			if ( psm != null )
				psm.verifyArtifactContainment(RoleManager.class, this.getClass());
			HashSet<Right> result = new HashSet<Right>();
			for ( Right r : this.rights )
				result.add(r);
			for ( Role child : children ) {
				Set<Right> childRights = child.getAllRights();
				result.addAll(childRights);
			}
			return result;
		}
	}
}
